﻿using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;

namespace Dev2419a_LunarLander3D
{
    class JogoA // Classe responsável pelo funcionamento do jogo
    {
        public struct VertexPositionColorNormal
        {
            public Vector3 Position;
            public Color Color;
            public Vector3 Normal;

            public readonly static VertexDeclaration VertexDeclaration = new VertexDeclaration
            (
                new VertexElement(0, VertexElementFormat.Vector3, VertexElementUsage.Position, 0),
                new VertexElement(sizeof(float) * 3, VertexElementFormat.Color, VertexElementUsage.Color, 0),
                new VertexElement(sizeof(float) * 3 + 4, VertexElementFormat.Vector3, VertexElementUsage.Normal, 0)
            );
        }

        SpriteFont fontHUD;

        GraphicsDevice device;

        Effect effect;

        VertexPositionColorNormal[] vertices;
        int[] indices;

        Matrix viewMatrix;
        Matrix projectionMatrix;

        private float angle = 0f;
        private float zoom = 1f;
        Vector3 posCamera = new Vector3(60, 80, -80);
        Vector3 posTerrain = Vector3.Zero;


        private int terrainWidth;
        private int terrainHeight;
        private float[,] heightData;

        VertexBuffer myVertexBuffer;
        IndexBuffer myIndexBuffer;

        //VIEWPORT

        /// <summary>
        /// viewport padrao, deve mostrar a cena toda por trás do módulo lunar...
        /// </summary>
        Viewport defaultViewport;

        /// <summary>
        /// viewport do mapa, deve mostrar a cena toda por cima do módulo lunar...
        /// </summary>
        Viewport mapViewport;

        /// <summary>
        /// projeção da viewport padrao
        /// </summary>
        //Matrix projectionMatrix;//ja tem

        /// <summary>
        /// projeção da viewport do mapa
        /// </summary>
        Matrix mapProjectionMatrix;

        //VIEWPORT

        public JogoA(ContentManager Content, GraphicsDeviceManager graphics)
        {
            this.device = graphics.GraphicsDevice;

   
            effect = Content.Load<Effect>("effects");

            Texture2D heightMap = Content.Load<Texture2D>("heightmap");
            LoadHeightData(heightMap);

            SetUpIndices();
            SetUpVertices();
            CalculateNormals();

            CopyToBuffers();

            SetUpCamera();

            //VIEWPORT

            defaultViewport = device.Viewport;
            mapViewport.Width = defaultViewport.Width / 4;
            mapViewport.Height = defaultViewport.Height / 4;
            mapViewport.X = 100;// (defaultViewport.Width - mapViewport.Width);
            mapViewport.Y = 0;

            //VIEWPORT

            //posição inicial do terreno
            posTerrain = new Vector3(-terrainWidth / 2.0f, 0, terrainHeight / 2.0f);
        }
       
        public void Update(GameTime gameTime, KeyboardState keyboard, KeyboardState oldKeyboard)
        {
            KeyboardState keyState = Keyboard.GetState();

            if (keyState.IsKeyDown(Keys.Left))
                angle += 0.05f;
            if (keyState.IsKeyDown(Keys.Right))
                angle -= 0.05f;

            if (keyState.IsKeyDown(Keys.OemPlus))
                zoom += 0.01f;
            if (keyState.IsKeyDown(Keys.OemMinus))
                zoom -= 0.01f;

            if (keyState.IsKeyDown(Keys.Up))
                posTerrain.Y++;
            if (keyState.IsKeyDown(Keys.Down))
                posTerrain.Y--;

            if (keyState.IsKeyDown(Keys.S))
                posTerrain.Z++;
            if (keyState.IsKeyDown(Keys.W))
                posTerrain.Z--;

            if (keyState.IsKeyDown(Keys.A))
                posTerrain.X--;
            if (keyState.IsKeyDown(Keys.D))
                posTerrain.X++;

            SetUpCamera();
        }

        public void Draw(GameTime gameTime, SpriteBatch spriteBatch)
        {
            device.Clear(ClearOptions.Target | ClearOptions.DepthBuffer, Color.Black, 1.0f, 0);

            //VIEWPORT
            device.Viewport = defaultViewport;
            //VIEWPORT

            effect.CurrentTechnique = effect.Techniques["Colored"];

            //And turn on the light:
            Vector3 lightDirection = new Vector3(1.0f, -1.0f, -1.0f);
            lightDirection.Normalize();
            effect.Parameters["xLightDirection"].SetValue(lightDirection);
            effect.Parameters["xAmbient"].SetValue(0.1f); effect.Parameters["xEnableLighting"].SetValue(true);

            effect.Parameters["xView"].SetValue(viewMatrix);
            effect.Parameters["xProjection"].SetValue(projectionMatrix);

            Matrix worldMatrix = Matrix.CreateTranslation(posTerrain) * Matrix.CreateRotationY(angle) * Matrix.CreateScale(zoom);

            effect.Parameters["xWorld"].SetValue(worldMatrix);

            foreach (EffectPass pass in effect.CurrentTechnique.Passes)
            {
                pass.Apply();
            }
            device.Indices = myIndexBuffer;
            device.SetVertexBuffer(myVertexBuffer);
            device.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, 0, vertices.Length, 0, indices.Length / 3);

            //VIEWPORT

            device.Viewport = mapViewport;

            //VIEWPORT

            effect.CurrentTechnique = effect.Techniques["Colored"];

            //And turn on the light:
            lightDirection = new Vector3(1.0f, -1.0f, -1.0f);
            lightDirection.Normalize();
            effect.Parameters["xLightDirection"].SetValue(lightDirection);
            effect.Parameters["xAmbient"].SetValue(0.1f); effect.Parameters["xEnableLighting"].SetValue(true);

            effect.Parameters["xView"].SetValue(viewMatrix);
            effect.Parameters["xProjection"].SetValue(projectionMatrix);

            worldMatrix = Matrix.CreateTranslation(posTerrain) * Matrix.CreateRotationY(angle) * Matrix.CreateScale(zoom);

            effect.Parameters["xWorld"].SetValue(worldMatrix);

            foreach (EffectPass pass in effect.CurrentTechnique.Passes)
            {
                pass.Apply();
            }
            device.Indices = myIndexBuffer;
            device.SetVertexBuffer(myVertexBuffer);
            device.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, 0, vertices.Length, 0, indices.Length / 3);

            //VIEWPORT
            device.Viewport = defaultViewport;
            //VIEWPORT
        }

        private void SetUpCamera()
        {
            viewMatrix = Matrix.CreateLookAt(posCamera, new Vector3(0, 0, 0), new Vector3(0, 1, 0));
            projectionMatrix = Matrix.CreatePerspectiveFieldOfView(MathHelper.PiOver4, device.Viewport.AspectRatio, 1.0f, 300.0f);
        }

        private void SetUpIndices()
        {
            indices = new int[(terrainWidth - 1) * (terrainHeight - 1) * 6];
            int counter = 0;
            for (int y = 0; y < terrainHeight - 1; y++)
            {
                for (int x = 0; x < terrainWidth - 1; x++)
                {
                    int lowerLeft = x + y * terrainWidth;
                    int lowerRight = (x + 1) + y * terrainWidth;
                    int topLeft = x + (y + 1) * terrainWidth;
                    int topRight = (x + 1) + (y + 1) * terrainWidth;

                    indices[counter++] = topLeft;
                    indices[counter++] = lowerRight;
                    indices[counter++] = lowerLeft;

                    indices[counter++] = topLeft;
                    indices[counter++] = topRight;
                    indices[counter++] = lowerRight;
                }
            }
        }

        private void SetUpVertices()
        {
            float minHeight = float.MaxValue;
            float maxHeight = float.MinValue;
            for (int x = 0; x < terrainWidth; x++)
            {
                for (int y = 0; y < terrainHeight; y++)
                {
                    if (heightData[x, y] < minHeight)
                        minHeight = heightData[x, y];
                    if (heightData[x, y] > maxHeight)
                        maxHeight = heightData[x, y];
                }
            }

            vertices = new VertexPositionColorNormal[terrainWidth * terrainHeight];
            for (int x = 0; x < terrainWidth; x++)
            {
                for (int z = 0; z < terrainHeight; z++)
                {
                    //vertices[x + y * terrainWidth].Position = new Vector3(x, 0, -y);
                    vertices[x + z * terrainWidth].Position = new Vector3(x, heightData[x, z], -z);

                    //vertices[x + y * terrainWidth].Color = Color.WhiteSmoke;


                    if (heightData[x, z] < minHeight + (maxHeight - minHeight) / 4)
                        vertices[x + z * terrainWidth].Color = Color.DarkGreen;

                    else if (heightData[x, z] < minHeight + (maxHeight - minHeight) * 2 / 4)
                        vertices[x + z * terrainWidth].Color = Color.CornflowerBlue;

                    else if (heightData[x, z] < minHeight + (maxHeight - minHeight) * 3 / 4)
                        vertices[x + z * terrainWidth].Color = Color.White;

                    else
                        vertices[x + z * terrainWidth].Color = Color.SpringGreen;

                }
            }
        }

        private void LoadHeightData(Texture2D heightMap)
        {
            terrainWidth = heightMap.Width;
            terrainHeight = heightMap.Height;

            Color[] heightMapColors = new Color[terrainWidth * terrainHeight];
            heightMap.GetData(heightMapColors);

            heightData = new float[terrainWidth, terrainHeight];
            for (int x = 0; x < terrainWidth; x++)
                for (int y = 0; y < terrainHeight; y++)
                    heightData[x, y] = heightMapColors[x + y * terrainWidth].R / 5.0f;
        }

        private void CalculateNormals()
        {
            for (int i = 0; i < vertices.Length; i++)
                vertices[i].Normal = new Vector3(0, 0, 0);

            for (int i = 0; i < indices.Length / 3; i++)
            {
                int index1 = indices[i * 3];
                int index2 = indices[i * 3 + 1];
                int index3 = indices[i * 3 + 2];

                Vector3 side1 = vertices[index1].Position - vertices[index3].Position;
                Vector3 side2 = vertices[index1].Position - vertices[index2].Position;
                Vector3 normal = Vector3.Cross(side1, side2);

                vertices[index1].Normal += normal;
                vertices[index2].Normal += normal;
                vertices[index3].Normal += normal;
            }
            for (int i = 0; i < vertices.Length; i++)
                vertices[i].Normal.Normalize();
        }

        private void CopyToBuffers()
        {
            myVertexBuffer = new VertexBuffer(device, VertexPositionColorNormal.VertexDeclaration, vertices.Length, BufferUsage.WriteOnly);
            myVertexBuffer.SetData(vertices);
            myIndexBuffer = new IndexBuffer(device, typeof(int), indices.Length, BufferUsage.WriteOnly);
            myIndexBuffer.SetData(indices);
        }
    }
}
